﻿using Abp.Dependency;
using Abp.Json;
using Abp.Runtime.Caching.Redis;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Text;
using Newtonsoft.Json;
using System.IO;
using System.Reflection;
using ProtoBuf;
using Abp.Runtime.Caching;
using System.Linq;

namespace FwProject.CatchUntil
{
    public class RedisCatch : DefaultRedisCacheSerializer, ITransientDependency
    {

        private const string TypeSeperator = "|";
        private const string ProtoBufPrefix = "PB^";

        /// <summary>
        ///     Creates an instance of the object from its serialized string representation.
        /// </summary>
        /// <param name="objbyte">String representation of the object from the Redis server.</param>
        /// <returns>Returns a newly constructed object.</returns>
        /// <seealso cref="IRedisCacheSerializer.Serialize" />
        public override object Deserialize(RedisValue objbyte)
        {
            string serializedObj = objbyte;
            if (!serializedObj.StartsWith(ProtoBufPrefix))
            {
                return DeserializeWithType(objbyte);
            }

            serializedObj = serializedObj.Substring(ProtoBufPrefix.Length);
            var typeSeperatorIndex = serializedObj.IndexOf(TypeSeperator, StringComparison.OrdinalIgnoreCase);
            var type = Type.GetType(serializedObj.Substring(0, typeSeperatorIndex));
            var serialized = serializedObj.Substring(typeSeperatorIndex + 1);
            var byteAfter64 = Convert.FromBase64String(serialized);

            using (var memoryStream = new MemoryStream(byteAfter64))
            {
                return Serializer.Deserialize(type, memoryStream);
            }

        }

        public static object DeserializeWithType(string serializedObj)
        {
            var serializerSettings = new JsonSerializerSettings();
            serializerSettings.Converters.Insert(0, new AbpDateTimeConverter());

            serializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

            AbpCacheData cacheData = AbpCacheData.Deserialize(serializedObj);

            return cacheData.Payload.FromJsonString(
                Type.GetType(cacheData.Type, true, true),
                serializerSettings);
        }


        /// <summary>
        ///     Produce a string representation of the supplied object.
        /// </summary>
        /// <param name="value">Instance to serialize.</param>
        /// <param name="type">Type of the object.</param>
        /// <returns>Returns a string representing the object instance that can be placed into the Redis cache.</returns>
        /// <seealso cref="IRedisCacheSerializer.Deserialize" />
        public override string Serialize(object value, Type type)
        {

            if (!type.GetTypeInfo().IsDefined(typeof(ProtoContractAttribute), false))
            {
                var settings = new JsonSerializerSettings();
                settings.ContractResolver = new AbpContractResolver();
                settings.MaxDepth = 3;
                settings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;

                var json = JsonConvert.SerializeObject(value, settings);

                var m = new AbpCacheData(SerializeType(value.GetType()).ToString(), json);
                return JsonConvert.SerializeObject(m); ;

                // base.Serialize(value, type);
            }

            using (var memoryStream = new MemoryStream())
            {
                Serializer.Serialize(memoryStream, value);
                var byteArray = memoryStream.ToArray();
                var serialized = Convert.ToBase64String(byteArray, 0, byteArray.Length);
                return $"{ProtoBufPrefix}{type.AssemblyQualifiedName}{TypeSeperator}{serialized}";
            }

        }

        public static string SerializeWithType(object obj, Type type)
        {

            var options = new JsonSerializerSettings();
            options.MaxDepth = 3;
            options.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            var serialized = JsonConvert.SerializeObject(obj, options);

            return string.Format(
                "{0}{1}{2}",
                type.AssemblyQualifiedName,
                TypeSeperator,
                serialized
                );
        }


        private static readonly IReadOnlyList<string> SystemAssemblyNames = new List<string> { "mscorlib", "System.Private.CoreLib" };
        private static StringBuilder SerializeType(Type type, bool withAssemblyName = true, StringBuilder typeNameBuilder = null)
        {
            typeNameBuilder = typeNameBuilder ?? new StringBuilder();

            if (type.DeclaringType != null)
            {
                SerializeType(type.DeclaringType, false, typeNameBuilder).Append('+');
            }
            else if (type.Namespace != null)
            {
                typeNameBuilder.Append(type.Namespace).Append('.');
            }

            typeNameBuilder.Append(type.Name);

            if (type.GenericTypeArguments.Length > 0)
            {
                SerializeTypes(type.GenericTypeArguments, '[', ']', typeNameBuilder);
            }

            if (!withAssemblyName)
            {
                return typeNameBuilder;
            }

            var assemblyName = type.GetTypeInfo().Assembly.GetName().Name;

            if (!SystemAssemblyNames.Contains(assemblyName))
            {
                typeNameBuilder.Append(", ").Append(assemblyName);
            }

            return typeNameBuilder;
        }

        private static StringBuilder SerializeTypes(Type[] types, char beginTypeDelimiter = '"', char endTypeDelimiter = '"', StringBuilder typeNamesBuilder = null)
        {
            if (types == null)
            {
                return null;
            }

            if (typeNamesBuilder == null)
            {
                typeNamesBuilder = new StringBuilder();
            }

            typeNamesBuilder.Append('[');

            for (int i = 0; i < types.Length; i++)
            {
                typeNamesBuilder.Append(beginTypeDelimiter);
                SerializeType(types[i], true, typeNamesBuilder);
                typeNamesBuilder.Append(endTypeDelimiter);

                if (i != types.Length - 1)
                {
                    typeNamesBuilder.Append(',');
                }
            }

            return typeNamesBuilder.Append(']');
        }
    }
}
